package net.tavion.angelboot.common.aop;

import net.tavion.angelboot.common.annotation.RedisCacheable;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static net.tavion.angelboot.common.enums.RedisType.*;

/**
 * @author : Rui
 * @date : 2019/5/5 16:20
 **/
@Aspect
@Configuration
@Order(value=Ordered.HIGHEST_PRECEDENCE+11)
public class RedisCacheableAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisCacheableAspect.class);

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Around("@annotation(redisCacheable)")
    public Object redisCacheable(ProceedingJoinPoint pjp, RedisCacheable redisCacheable) throws Throwable {
        //get param
        Object[] args = pjp.getArgs();

        StringBuilder redisKeyBuild = new StringBuilder();
        String unHandleKey = redisCacheable.key();

        //must split ":"
        String[] unHandle = unHandleKey.split(":");
        for (String temp : unHandle){
            String[] splitArray = temp.split("\\.");
            if (splitArray.length > 1) {
                //#p0.xxx  get param seq
                String regEx="[^0-9]";
                Pattern p = Pattern.compile(regEx);
                Matcher m = p.matcher(splitArray[0]);
                int seq = Integer.parseInt(m.replaceAll(""));
                //get filed value
                Field field = args[seq].getClass().getDeclaredField(splitArray[1]);
                field.setAccessible(true);
                Object object = field.get(args[seq]);
                redisKeyBuild.append(object.toString()).append(":");
            } else {
                //#p0
                if(temp.startsWith("#")) {
                    String regEx="[^0-9]";
                    Pattern p = Pattern.compile(regEx);
                    Matcher m = p.matcher(temp);
                    int seq = Integer.parseInt(m.replaceAll(""));
                    redisKeyBuild.append(args[seq]).append(":");
                } else {
                    // Constant
                    redisKeyBuild.append(temp).append(":");
                }
            }
        }
        //delete :
        redisKeyBuild.deleteCharAt(redisKeyBuild.length()-1);
        String redisKey = redisKeyBuild.toString();
        Object redisResult;
        switch (redisCacheable.redisType()){
            case OBJECT:
                redisResult = redisTemplate.opsForValue().get(redisKey);
                if(null != redisResult){
                    return redisResult;
                }
                break;
            case SET:
                redisResult = redisTemplate.opsForSet().members(redisKey);
                if (((Set)redisResult).size() > 0){
                    return redisResult;
                }
                break;
            case HASH:
                redisResult = redisTemplate.opsForHash().entries(redisKey);
                if(!((HashMap<Object, Object>)redisResult).isEmpty()){
                    return redisResult;
                }
                break;
            case LIST:
                Long listSize = redisTemplate.opsForList().size(redisKey);
                redisResult = redisTemplate.opsForList().range(redisKey,0,listSize);
                if(((List)redisResult).size() > 0){
                    return redisResult;
                }
                break;
            default:
        }
        //get result
        Object result = pjp.proceed();
        if (null == result) {
            return null;
        }
        switch (redisCacheable.redisType()){
            case OBJECT:
                redisTemplate.opsForValue().set(redisKey,result);
                break;
            case SET:
                Set<Object> setResult  = (Set<Object>)result;
                for(Object element : setResult){
                    redisTemplate.opsForSet().add(redisKey,element);
                }
                break;
            case HASH:
                redisTemplate.opsForHash().putAll(redisKey,(Map<Object, Object>) result);
                break;
            case LIST:
                List list = (List)result;
                if(list.size()<=0) {
                    return result;
                }
                redisTemplate.opsForList().rightPushAll(redisKey,list);
                break;
            default:
                return result;
        }
        if (redisCacheable.cacheTime() > 0) {
            redisTemplate.expire(redisKey, redisCacheable.cacheTime(), redisCacheable.timeUnit());
        }
        return result;
    }
}
